import { decode, encode } from "cbor-x";
import conn_port from "./conn_port";
import { Frame } from "./frame";
import { CMD_LOG, CMD_STATUS } from "./service";
import { deviceStatus } from "../utils/react";

const router = new Map<number, (f: Frame) => void>();

export const TYPE_ACK = 0;
export const TYPE_PING = 1;
export const TYPE_PONG = 2;
export const TYPE_SIMPLE_REQ = 3;
export const TYPE_SIMPLE_RES = 4;
export const TYPE_NOTIFY = 5;
export const TYPE_NOTIFY_ACK = 6;
export const TYPE_SESSION = 7;
export const TYPE_REQ = 8;
export const TYPE_RES = 9;

const HEAD0 = 0xE1;
const HEAD1 = 0x1E;
const END = 0xEF;

export function make_empty(type: number, seq: number) {
    return [
        HEAD0, HEAD1, 7, type, seq, (0 ^ type ^ seq), END
    ]
}

function xor_sum(buf: number[], index: number, len: number) {
    let s = 0;
    for (let i = 0; i < len; i ++) {
        s ^= buf[index + i];
    }
    return s;
}

export function make_req<T>(type: number, seq: number, cmd: number, value: T) {
    const data = encode(value) as Uint8Array;
    const buf = new Array<number>(data.length + 8);
    buf[0] = HEAD0;
    buf[1] = HEAD1;
    buf[2] = (8 + data.length) & 0xFF;
    buf[3] = type;
    buf[4] = seq;
    buf[5] = cmd;
    for (let i = 0; i < data.length; i ++) {
        buf[6 + i] = data[i];
    }
    const s = xor_sum(buf, 3, 3 + data.length);
    buf[6 + data.length] = s;
    buf[7 + data.length] = END;
    return buf;
}

export function createResp(type: number, seq: number, timeout: number, msg: string = '超时') {
    const id = (type << 8) + seq;
    return new Promise<Frame>((resolve, reject) => {
        const timer = setTimeout(() => {
            router.delete(id);
            reject(msg);
        }, timeout);
        router.set(id, (f: Frame) => {
            clearTimeout(timer);
            resolve(f)
        })
    });
}

export function dispatch(buf: number[]) {
    const frame = new Frame(buf);
    switch (frame.type()) {
        case TYPE_ACK:
        case TYPE_PONG:
        case TYPE_SIMPLE_RES:
        case TYPE_RES:
            const f = router.get(frame.id());
            if (f == null) {
                console.error('no handler frame', frame);
                return;
            }
            f(frame);
            break;

        case TYPE_PING:
            conn_port.write(make_empty(TYPE_PONG, frame.seq()));
            break;

        case TYPE_NOTIFY_ACK:
            conn_port.write(make_empty(TYPE_ACK, frame.seq()));
            on_notify(frame);
            break;
        case TYPE_NOTIFY:
            on_notify(frame);
            break;
    }
}


function on_notify(frame: Frame) {
    switch (frame.cmd()) {
        case CMD_STATUS:
            on_status(frame);
        break;

        case CMD_LOG:
            on_log(frame);
        break;
    }
}

function on_status(frame: Frame) {
    const status = frame.parse() as number;
    deviceStatus.version = status;
}

function on_log(frame: Frame) {
    const log = frame.parse() as string;
    console.log(log);
}
